#include <fhepython/PyNode.h>
#include <fhepython/PyNodeDesc.h>
#include <fhepython/PyCall.h>
#include <fhepython/PyEnv.h>
#include <fhepython/PyValVar.h>
#include <fhepython/PyFunc.h>
#include <fhe/core/NodeFactory.h>

namespace fhe
{
  using namespace core;
  namespace python
  {
    std::string PyNode::regModule_;
    
    PyNode::PyNode( const std::string& module, const std::string& type ) 
      : node_( NodeFactory::instance().buildNode( module, type ) )
    {
    }
    
    PyNode::PyNode( const Node::Ptr& node )
      : node_( node )
    {
    }
    
    PyNode::~PyNode()
    {
    }
    
    boost::python::object PyNode::define()
    {
      boost::python::scope s = boost::python::class_<PyNode>( "Node", boost::python::init<std::string,std::string>() )
        .add_property( "module", &PyNode::module )
        .add_property( "type", &PyNode::type )
        .add_property( "parent", boost::python::make_function( &PyNode::parent, boost::python::return_value_policy< boost::python::manage_new_object >() ) )
        .add_property( "children", &PyNode::children )
        .add_property( "funcs", &PyNode::funcs )
        .add_property( "vars", &PyNode::vars )
        .def( "hasChild", &PyNode::hasChild )
        .def( "attachToParent", &PyNode::attachToParent )
        .def( "detachFromParent", &PyNode::detachFromParent )
        .def( "addChild", &PyNode::addChild )
        .def( "removeChild", &PyNode::removeChild )
        .def( "func", &PyNode::addFunc )
        .def( "__getattr__", &PyNode::getAttr )
        .def( "__setattr__", &PyNode::setAttr )
        .def( "__eq__", &PyNode::eq )
      ;
      
      boost::python::list nodeTypes;
      for ( NodeFactory::NodeDescIterator nodeDesc = NodeFactory::instance().nodeDescsBegin(); nodeDesc != NodeFactory::instance().nodeDescsEnd(); ++nodeDesc )
      {
        nodeTypes.append( boost::python::make_tuple( (*nodeDesc)->module(), (*nodeDesc)->name() ) );
      }
      s.attr( "types" ) = nodeTypes;
      
      return s;
    }
    
    std::string PyNode::module() const
    {
      return node_->module();
    }
    
    std::string PyNode::type() const
    {
      return node_->type();
    }
    
    PyNode* PyNode::parent() const
    {
      return node_->parent() ? new PyNode( node_->parent() ) : 0;
    }
    
    boost::python::list PyNode::children() const
    {
      boost::python::list children;
      for ( Node::ChildIterator child = node_->childrenBegin(); child != node_->childrenEnd(); ++child )
      {
        children.append( new PyNode( *child ) );
      }
      return children;
    }
    
    bool PyNode::hasChild( PyNode* child ) const
    {
      return child && node_->hasChild( child->node_ );
    }
    
    void PyNode::attachToParent( PyNode* parent )
    {
      FHE_ASSERT( parent != 0 );
      node_->attachToParent( parent->node_ );
    }
    
    void PyNode::detachFromParent()
    {
      node_->detachFromParent();
    }
    
    void PyNode::addChild( PyNode* child )
    {
      FHE_ASSERT( child != 0 );
      node_->addChild( child->node_ );
    }
    
    void PyNode::removeChild( PyNode* child )
    {
      FHE_ASSERT( child != 0 );
      node_->removeChild( child->node_ );
    }
    
    boost::python::list PyNode::funcs() const
    {
      boost::python::list names;
      for ( Node::FuncIterator func = node_->funcsBegin(); func != node_->funcsEnd(); ++func )
      {
        names.append( func->first );
      }
      return names;
    }
    
    boost::python::list PyNode::vars() const
    {
      boost::python::list names;
      for ( Node::VarIterator var = node_->varsBegin(); var != node_->varsEnd(); ++var )
      {
        names.append( var->first );
      }
      return names;
    }
    
    boost::python::object PyNode::getAttr( const std::string& name ) const
    {
      if ( node_->hasVar( name ) )
      {
        return PyEnv::instance().convertVal( node_->getVar( name )->get() );
      }
      else if ( node_->hasFunc( name ) )
      {
        return boost::python::object( PyCall( node_->getFunc( name ) ) );
      }
      else
      {
        FHE_ERROR( "unknown attr %s", name.c_str() );
        return boost::python::object();
      }
    }
    
    bool PyNode::eq( PyNode* node ) const
    {
      return node && node_ == node->node_;
    }
    
    boost::python::object PyNode::reg( const std::string& module )
    {
      regModule_ = module;
      return boost::python::make_function( &PyNode::regClosure );
    }
    
    void PyNode::regClosure( boost::python::object obj )
    {
      INodeDesc::Ptr nodeDesc( new PyNodeDesc( obj ) );
      nodeDesc->setModule( regModule_ );
      nodeDesc->setName( boost::python::extract<std::string>( obj.attr( "__name__" ) ) );
      NodeFactory::instance().addNodeDesc( nodeDesc );
    }
    
    void PyNode::setAttr( const std::string& name, boost::python::object obj )
    {
      if ( node_->hasVar( name ) )
      {
        node_->set( name, PyEnv::instance().convertVal( obj ) );
      }
      else
      {
        node_->addVar( IVar::Ptr( new PyValVar( name, obj ) ) );
      }
    }
    
    void PyNode::addFunc( boost::python::object func )
    {
      node_->addFunc( IFunc::Ptr( new PyFunc( node_.get(), func ) ) );
    }
  }
}
